import java.nio.*;
import javax.media.opengl.*;
import jocode.*;
import jomodel.*;
import processing.opengl.*;

// handle for texture
int sphereTextureHandle = 0;
int groundTextureHandle = 0;
// light position: 
//  if last value is 0, then this describes light direction
//  if 1, then light position
float lightPosition[]= { -2f, 2f, 2f, 0f };
// camera position
float[] cameraPos = {0f,3f,20f};

// two cameras and a cam to move them around scene
JOCamera camera1 = new JOCamera();
JOCamera camera2 = new JOCamera();
JOCam cam = new JOCam(camera1);

// vectors used to orient airplane motion
JOVector up = new JOVector(0, 1, 0);
JOVector origin = new JOVector(0, 0, 0);

// for earth rotation
float degrees  = 0;
float rotation = 0;

// model of airplane and sphere displaylist for earth
JOModel airplane;
int earth;

// shadow handler will draw a shadow on floor plane
JOShadowOnPlane airplaneShadow;

public JOVector airplanePos;

FloatBuffer bbmatrix = JOApp.allocFloats(16);

PFont font;
    
void setup() {
  size(800, 600, OPENGL);
  font = loadFont("Tahoma.vlw");
  textFont(font);
  
  JOBridgeP5.init(this);
  
  // create texture for sphere
  sphereTextureHandle = JOApp.makeTexture("earth.gif");

  // create texture for ground plane
  groundTextureHandle = JOApp.makeTexture("grass_1_512.jpg", true, true);

  // set camera 1 position
  camera1.setCamera(0,   4, 15, 
                    0, -.3, -1,
                    0,   1, 0);

  // load the airplane model and make it a display list
  airplane = new JOModel("JetFire/JetFire.obj");
  airplane.mesh.regenerateNormals();
  airplane.makeDisplayList();

  // make a sphere display list
  earth = JOApp.beginDisplayList(); 
  {
      JOApp.renderSphere();
  }
  JOApp.endDisplayList();

  // make a shadow handler
  // params:
  //      the light position,
  //      the plane the shadow will fall on,
  //      the color of the shadow,
  //      this application,
  //      the function that draws all objects that cast shadows
  airplaneShadow = new JOShadowOnPlane(lightPosition, 
                                       new float[] { 0f, 1f, 0f, 3f }, 
                                       null, this, 
                                       JOApp.method(this,"drawObjects"));
}

// set the field of view and view depth.
void setPerspective() {
  // select projection matrix (controls perspective)
  JOApp.gl.glMatrixMode(GL.GL_PROJECTION);
  JOApp.gl.glLoadIdentity();
  // fovy, aspect ratio, zNear, zFar
  JOApp.glu.gluPerspective(50f,                // zoom in or out of view
                           JOApp.aspectRatio,  // shape of viewport rectangle
                           .1f,                // Min Z: how far from eye position does view start
                           500f);              // max Z: how far from eye position does view extend
  // return to modelview matrix
  JOApp.gl.glMatrixMode(GL.GL_MODELVIEW);
}

void draw() {
  degrees += 30f * (1f / frameRate);

  JOApp.begin();
  {
      // clear depth buffer and color
      JOApp.gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

      // setup and enable perspective
      setPerspective();
      
      // create a light (diffuse light, ambient light, position)
      JOApp.setLight(GL.GL_LIGHT1,
              new float[] { 1f, 1f, 1f, 1f },
              new float[] { 0.5f, 0.5f, .53f, 1f },
              new float[] { 1f, 1f, 1f, 1f },
              lightPosition );

      // create a directional light (light green, to simulate reflection off grass)
      JOApp.setLight(GL.GL_LIGHT2,
              new float[] { 0.15f, 0.4f, 0.1f, 1.0f },  // diffuse color
              new float[] { 0.0f, 0.0f, 0.0f, 1.0f },   // ambient
              new float[] { 0.0f, 0.0f, 0.0f, 1.0f },   // specular
              new float[] { 0.0f, -10f, 0.0f, 0f } );   // direction (pointing up)

      // enable lighting and texture rendering
      JOApp.gl.glEnable(GL.GL_LIGHTING);
      JOApp.gl.glEnable(GL.GL_TEXTURE_2D);

      // enable alpha transparency (so text will have transparent background)
      JOApp.gl.glEnable(GL.GL_BLEND);
      JOApp.gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

      // place airplane in orbit around ball, and place camera slightly above airplane
      airplanePos = JOVector.rotationVector(degrees).mult(8);
      camera2.moveTo(airplanePos.x, airplanePos.y+.53f, airplanePos.z);

      // align airplane and camera2 (perpendicular to the radius and up vector)
      JOVector airplaneDirection = JOVector.crossProduct(up, airplanePos);
      camera2.viewDir( airplaneDirection );  // point camera in direction of airplane motion

      // user keystrokes adjust camera position
      cam.handleNavKeys(1f / frameRate);

      // combine user camera motion with current camera position (so user can look around while on the airplane)
      float apRot = camera2.rotatedY;  // how much is camera rotated?
      camera2.rotatedY = 0;            // zero out rotation
      camera2.rotateY(apRot);          // set rotation again (camera will add rotation to its current view direction)

      // clear depth buffer and color
      JOApp.gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

      // select model view for subsequent transforms
      JOApp.gl.glMatrixMode(GL.GL_MODELVIEW);
      JOApp.gl.glLoadIdentity();

      // do gluLookAt() with camera position, direction, orientation
      cam.render();

      // draw the ground plane
      JOApp.gl.glPushMatrix();
      {
          JOApp.gl.glTranslatef(0f, -3f, 0f); // down a bit
          JOApp.gl.glScalef(15f, .01f, 15f);
          JOApp.gl.glBindTexture(GL.GL_TEXTURE_2D, groundTextureHandle);
          JOApp.renderCube();
      }
      JOApp.gl.glPopMatrix();

      // invokes the drawObjects() method to create shadows for objects in the scene
      airplaneShadow.drawShadow();

      // draw sphere at center (rotate 10 degrees per second)
      rotation += 10f * (1f / frameRate);

      // draw the scene (after we draw the shadows, so everything layers correctly)
      drawObjects();

      // Place the light.  Light will move with the rest of the scene
      JOApp.setLightPosition(GL.GL_LIGHT1, lightPosition);
  }
  JOApp.end();
  
  text("Use arrow keys to navigate:", 30, 45);
  text("Left-Right arrows rotate camera", 30, 80);
  text("Up-Down arrows move camera forward and back", 30, 100);
  text("PageUp-PageDown move vertically", 30, 120);
  text("SPACE key switches cameras", 30, 140);
}

void drawObjects() {
  // draw the airplane
  JOApp.gl.glPushMatrix();
  {
    // place plane at orbit point, and orient it toward origin
    billboardPoint(airplanePos, origin, up);
    // turn plane toward direction of motion
    JOApp.gl.glRotatef(-90, 0, 1, 0);
    // make it big
    JOApp.gl.glScalef(4f, 4f, 4f);
    airplane.render();
    // reset material, since model.render() will alter current material settings
    JOApp.setMaterial(new float[] { .8f, .8f, .7f, 1f }, .4f);
  }
  JOApp.gl.glPopMatrix();

  // draw the earth
  JOApp.gl.glPushMatrix();
  {
    JOApp.gl.glRotatef(rotation, 0, 1, 0);  // rotate around Y axis
    JOApp.gl.glScalef(2f, 2f, 2f);          // scale up
    JOApp.gl.glBindTexture(GL.GL_TEXTURE_2D, sphereTextureHandle);
    JOApp.callDisplayList(earth);
  }
  JOApp.gl.glPopMatrix();
}

// Given position of object and target, create matrix to
//  orient object so it faces target.
public void billboardPoint(JOVector bbPos, JOVector targetPos, JOVector targetUp) {
  // direction the billboard will be facing (looking):
  JOVector look = JOVector.sub(targetPos,bbPos).normalize();
  
  // billboard Right vector is perpendicular to Look and Up (the targets Up vector)
  JOVector right = JOVector.crossProduct(targetUp,look).normalize();
  
  // billboard Up vector is perpendicular to Look and Right
  JOVector up = JOVector.crossProduct(look,right).normalize();
  
  // Create a 4x4 matrix that will orient the object at bbPos to face targetPos
  JOMatrix.createBillboardMatrix(bbmatrix, right, up, look, bbPos);
  
  // apply the billboard matrix
  JOApp.gl.glMultMatrixf(bbmatrix);
}

void keyPressed() {
  cam.keyPressed(keyEvent);
    
  if (key == ' ') {
    cam.setCamera((cam.camera == camera1)? camera2 : camera1);
  }
}

void keyReleased() {
  cam.keyReleased(keyEvent);
}

void keyTyped() {
  cam.keyTyped(keyEvent);
}
